package org.mybatis.generator;

import org.management.utils.DateUtils;
import org.mybatis.config.JavaGeneratorConfiguration;
import org.mybatis.context.ContextOverride;
import org.mybatis.generator.api.GeneratedJavaFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.CompilationUnit;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.stream.Collectors;

/**
 * 功能描述
 *
 * @author jy
 * @date 2019/3/7
 * @description
 * @project management
 */
public class CodeGenerator extends PluginAdapter {
    @Override
    public boolean validate(List<String> list) {
        return true;
    }

    /**
     * 生成额外java文件
     */
    @Override
    public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {
        ContextOverride context = (ContextOverride) introspectedTable.getContext();
        JavaGeneratorConfiguration javaGeneratorConfiguration;
        if (( javaGeneratorConfiguration = context.getServiceGeneratorConfiguration()) == null)
            return null;
        CompilationUnit addServiceInterface = addServiceInterface(introspectedTable, javaGeneratorConfiguration);
        GeneratedJavaFile gjfServiceInterface = new GeneratedJavaFile(addServiceInterface, javaGeneratorConfiguration.getTargetProject(),
                this.context.getProperty("javaFileEncoding"), this.context.getJavaFormatter());
        if (( javaGeneratorConfiguration = context.getServiceImplGeneratorConfiguration()) == null) {
            return null;
        }
        CompilationUnit addServiceImplClazz = addServiceImplClazz(introspectedTable, javaGeneratorConfiguration);
        GeneratedJavaFile gjfServiceImplClazz = new GeneratedJavaFile(addServiceImplClazz, javaGeneratorConfiguration.getTargetProject(),
                this.context.getProperty("javaFileEncoding"), this.context.getJavaFormatter());
        if (( javaGeneratorConfiguration = context.getControllerGeneratorConfiguration()) == null) {
            return null;
        }
        CompilationUnit addControllerClazz = addControllerClazz(introspectedTable, javaGeneratorConfiguration);
        GeneratedJavaFile gjfControllerClazz = new GeneratedJavaFile(addControllerClazz, javaGeneratorConfiguration.getTargetProject(),
                this.context.getProperty("javaFileEncoding"), this.context.getJavaFormatter());
        List<GeneratedJavaFile> list = new ArrayList<>();
        list.add(gjfServiceInterface);
        list.add(gjfServiceImplClazz);
        list.add(gjfControllerClazz);
        return list;
    }

    private CompilationUnit addServiceInterface(IntrospectedTable introspectedTable, JavaGeneratorConfiguration javaGeneratorConfiguration) {
        String entityClazzType = introspectedTable.getBaseRecordType();
        String tableComment = introspectedTable.getRemarks();
        String serviceSuperPackage = javaGeneratorConfiguration.getTargetPackage();
        String domainObjectName = generateDomainName(introspectedTable);
        StringBuilder builder = new StringBuilder();
        FullyQualifiedJavaType superInterfaceType = new FullyQualifiedJavaType(
                builder.append("BaseService<")
                        .append(entityClazzType)
                        .append(">").toString());
        Interface serviceInterface = new Interface(
                builder.delete(0, builder.length())
                        .append(serviceSuperPackage)
                        .append(".")
                        .append(domainObjectName)
                        .append("Service")
                        .toString()
        );
        String userName = System.getProperty("user.name");
        String date = DateUtils.getToday("yyyy/MM/dd");
        serviceInterface.addSuperInterface(superInterfaceType);
        serviceInterface.setVisibility(JavaVisibility.PUBLIC);
        FullyQualifiedJavaType baseServiceInstance = FullyQualifiedJavaTypeProxyFactory.getBaseServiceInstance();
        FullyQualifiedJavaType modelJavaType = new FullyQualifiedJavaType(entityClazzType);
        serviceInterface.addImportedType(baseServiceInstance);
        serviceInterface.addImportedType(modelJavaType);
        serviceInterface.addJavaDocLine(
                "/**\n" +
                " * 功能描述\n" +
                " * "+tableComment+"接口类\n" +
                " * @author "+userName+"\n" +
                " * @date "+date+"\n" +
                " * @description\n" +
                " * @project management\n" +
                " */");
        this.additionalServiceMethods(introspectedTable, serviceInterface);
        return serviceInterface;
    }

    private CompilationUnit addServiceImplClazz(IntrospectedTable introspectedTable, JavaGeneratorConfiguration javaGeneratorConfiguration) {
        String entityClazzType = introspectedTable.getBaseRecordType();
        String tableComment = introspectedTable.getRemarks();
        String serviceImplSuperPackage = javaGeneratorConfiguration.getTargetPackage();
        String javaMapperType = introspectedTable.getMyBatis3JavaMapperType();
        ContextOverride context = (ContextOverride) introspectedTable.getContext();
        String serviceSuperPackage = context.getServiceGeneratorConfiguration().getTargetPackage();
        String domainObjectName = generateDomainName(introspectedTable);
        StringBuilder builder = new StringBuilder();
        FullyQualifiedJavaType superClazzType = new FullyQualifiedJavaType(
                builder.append("BaseServiceImpl<")
                        .append(entityClazzType)
                        .append(">")
                        .toString()
        );
        FullyQualifiedJavaType implInterfaceType = new FullyQualifiedJavaType(
                builder.delete(0, builder.length())
                        .append(serviceSuperPackage)
                        .append(".")
                        .append(domainObjectName)
                        .append("Service")
                        .toString()
        );

        TopLevelClass serviceImplClazz = new TopLevelClass(
                builder.delete(0, builder.length())
                        .append(serviceImplSuperPackage)
                        .append(".")
                        .append(domainObjectName)
                        .append("ServiceImpl")
                        .toString()
        );
        serviceImplClazz.addSuperInterface(implInterfaceType);
        serviceImplClazz.setSuperClass(superClazzType);
        serviceImplClazz.setVisibility(JavaVisibility.PUBLIC);
        serviceImplClazz.addAnnotation("@Service");
        FullyQualifiedJavaType baseServiceInstance = FullyQualifiedJavaTypeProxyFactory.getBaseServiceImplInstance();
        FullyQualifiedJavaType modelJavaType = new FullyQualifiedJavaType(entityClazzType);
        serviceImplClazz.addImportedType(new FullyQualifiedJavaType("org.springframework.stereotype.Service"));
        serviceImplClazz.addImportedType(baseServiceInstance);
        serviceImplClazz.addImportedType(modelJavaType);
        serviceImplClazz.addImportedType(implInterfaceType);
        String mapperName = builder.delete(0, builder.length())
                .append(Character.toLowerCase(domainObjectName.charAt(0)))
                .append(domainObjectName.substring(1))
                .append("Dao")
                .toString();
        String userName = System.getProperty("user.name");
        String date = DateUtils.getToday("yyyy/MM/dd");
        FullyQualifiedJavaType JavaMapperType = new FullyQualifiedJavaType(javaMapperType);
        Field mapperField = new Field();
        mapperField.setVisibility(JavaVisibility.PUBLIC);
        mapperField.setType(JavaMapperType);
        mapperField.setName(mapperName);
        mapperField.addJavaDocLine("");
        mapperField.addAnnotation("@Resource");
        serviceImplClazz.addField(mapperField);
        serviceImplClazz.addJavaDocLine(
                "/**\n" +
                        " * 功能描述\n" +
                        " * "+tableComment+"服务类\n" +
                        " * @author "+userName+"\n" +
                        " * @date "+date+"\n" +
                        " * @description\n" +
                        " * @project management\n" +
                        " */");
        serviceImplClazz.addImportedType(JavaMapperType);
        serviceImplClazz.addImportedType(new FullyQualifiedJavaType("javax.annotation.Resource"));
        this.additionalServiceImplMethods(introspectedTable, serviceImplClazz, mapperName);
        return serviceImplClazz;
    }

    private CompilationUnit addControllerClazz(IntrospectedTable introspectedTable, JavaGeneratorConfiguration javaGeneratorConfiguration) {
        String entityClazzType = introspectedTable.getBaseRecordType();
        String controllerSuperPackage = javaGeneratorConfiguration.getTargetPackage();
        ContextOverride context = (ContextOverride) introspectedTable.getContext();
        String serviceSuperPackage = context.getServiceGeneratorConfiguration().getTargetPackage();
        String domainObjectName = generateDomainName(introspectedTable);
        String path = getRequestMappingPath(domainObjectName);
        StringBuilder builder = new StringBuilder();
        String tableComment = introspectedTable.getRemarks();
        FullyQualifiedJavaType superClazzType = new FullyQualifiedJavaType(
                builder.append("BaseController<")
                        .append(entityClazzType)
                        .append(">")
                        .toString()
        );
        TopLevelClass controllerImplClazz = new TopLevelClass(
                builder.delete(0, builder.length())
                        .append(controllerSuperPackage)
                        .append(".")
                        .append(domainObjectName)
                        .append("Controller")
                        .toString()
        );
        String mapperName = builder.delete(0, builder.length())
                .append(Character.toLowerCase(domainObjectName.charAt(0)))
                .append(domainObjectName.substring(1)).append("Service")
                .toString();
        String userName = System.getProperty("user.name");
        String date = DateUtils.getToday("yyyy/MM/dd");
        controllerImplClazz.setSuperClass(superClazzType);
        controllerImplClazz.setVisibility(JavaVisibility.PUBLIC);
        controllerImplClazz.addAnnotation("@RestController");
        controllerImplClazz.addAnnotation("@Api(tags = \""+tableComment+"模块\")");
        controllerImplClazz.addAnnotation("@RequestMapping(value=\""+path+"\")");
        FullyQualifiedJavaType baseServiceInstance = FullyQualifiedJavaTypeProxyFactory.getBaseControllerInstance();
        FullyQualifiedJavaType modelJavaType = new FullyQualifiedJavaType(entityClazzType);
        controllerImplClazz.addImportedType(new FullyQualifiedJavaType("org.springframework.web.bind.annotation.RestController"));
        controllerImplClazz.addImportedType(new FullyQualifiedJavaType("org.springframework.web.bind.annotation.RequestMapping"));
        controllerImplClazz.addImportedType(new FullyQualifiedJavaType("io.swagger.annotations.Api"));
        controllerImplClazz.addImportedType(baseServiceInstance);
        controllerImplClazz.addImportedType(modelJavaType);
        FullyQualifiedJavaType JavaMapperType = new FullyQualifiedJavaType(serviceSuperPackage +"."+ domainObjectName + "Service");
        Field serviceField = new Field();
        serviceField.setVisibility(JavaVisibility.PRIVATE);
        serviceField.setType(JavaMapperType);// Mapper.java
        serviceField.setName(mapperName);
        serviceField.addJavaDocLine("");
        serviceField.addAnnotation("@Resource");
        controllerImplClazz.addField(serviceField);
        controllerImplClazz.addJavaDocLine(
                "/**\n" +
                        " * 功能描述\n" +
                        " * "+tableComment+"控制类\n" +
                        " * @author "+userName+"\n" +
                        " * @date "+date+"\n" +
                        " * @description\n" +
                        " * @project management\n" +
                        " */");
        controllerImplClazz.addImportedType(JavaMapperType);
        controllerImplClazz.addImportedType(new FullyQualifiedJavaType("javax.annotation.Resource"));
        this.additionalServiceImplMethods(introspectedTable, controllerImplClazz, mapperName);
        return controllerImplClazz;
    }

    private String getRequestMappingPath(String domainObjectName) {
        StringBuilder sb = new StringBuilder();
        char[] chars = domainObjectName.toCharArray();
        for (char letter : chars) {
            if (Character.isUpperCase(letter)) {
                sb.append("/");
            }
            sb.append(letter);
        }
        return sb.toString().toLowerCase();
    }

    private String generateDomainName(IntrospectedTable introspectedTable) {
        String newDomainName = introspectedTable.getFullyQualifiedTable().getIntrospectedTableName();
        StringTokenizer stringTokenizer = new StringTokenizer(newDomainName,"_");
        stringTokenizer.nextToken();
        StringBuilder sb = new StringBuilder();
        while (stringTokenizer.hasMoreTokens()) {
            char[] chars = stringTokenizer.nextToken().toCharArray();
            chars[0] = (char)(chars[0]- 32);
            sb.append(String.valueOf(chars));
        }
        return sb.toString();
    }

    private void additionalServiceMethods(IntrospectedTable introspectedTable, Interface serviceInterface) {
        if (this.notHasBLOBColumns(introspectedTable))
            return;
        introspectedTable.getGeneratedJavaFiles().stream().filter(file -> file.getCompilationUnit().isJavaInterface()
                && file.getCompilationUnit().getType().getShortName().endsWith("Mapper")).map(GeneratedJavaFile::getCompilationUnit).forEach(
                compilationUnit -> ((Interface) compilationUnit).getMethods().forEach(
                        m -> serviceInterface.addMethod(this.additionalServiceLayerMethod(serviceInterface, m))));
    }

    private void additionalServiceImplMethods(IntrospectedTable introspectedTable, TopLevelClass clazz,
                                                String mapperName) {
        if (this.notHasBLOBColumns(introspectedTable))
            return;
        introspectedTable.getGeneratedJavaFiles().stream().filter(file -> file.getCompilationUnit().isJavaInterface()
                && file.getCompilationUnit().getType().getShortName().endsWith("Mapper")).map(GeneratedJavaFile::getCompilationUnit).forEach(
                compilationUnit -> ((Interface) compilationUnit).getMethods().forEach(m -> {
                    Method serviceImplMethod = this.additionalServiceLayerMethod(clazz, m);
                    serviceImplMethod.addAnnotation("@Override");
                    serviceImplMethod.addBodyLine(this.generateBodyForServiceImplMethod(mapperName, m));
                    clazz.addMethod(serviceImplMethod);
                }));
    }


    private boolean notHasBLOBColumns(IntrospectedTable introspectedTable) {
        return !introspectedTable.hasBLOBColumns();
    }

    private Method additionalServiceLayerMethod(CompilationUnit compilation, Method m) {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setName(m.getName());
        List<Parameter> parameters = m.getParameters();
        method.getParameters().addAll(parameters.stream().peek(param -> param.getAnnotations().clear()).collect(Collectors.toList()));
        method.setReturnType(m.getReturnType());
        compilation.addImportedType(
                new FullyQualifiedJavaType(m.getReturnType().getFullyQualifiedNameWithoutTypeParameters()));
        return method;
    }

    private String generateBodyForServiceImplMethod(String mapperName, Method m) {
        StringBuilder sbf = new StringBuilder("return ");
        sbf.append(mapperName).append(".").append(m.getName()).append("(");
        boolean singleParam = true;
        for (Parameter parameter : m.getParameters()) {
            if (singleParam)
                singleParam = false;
            else
                sbf.append(", ");
            sbf.append(parameter.getName());

        }
        sbf.append(");");
        return sbf.toString();
    }

}